#include "llvm/Support/CommandLine.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Frontend/TextDiagnosticPrinter.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/ASTConsumers.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Basic/TargetOptions.h"
#include "llvm/Support/Host.h"
#include "clang/Basic/FileManager.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/PreprocessorOptions.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Lex/HeaderSearch.h"
#include "clang/AST/ASTConsumer.h"
#include "llvm/Support/raw_ostream.h"
#include "clang/Parse/ParseAST.h"
#include "clang/AST/ASTContext.h"

#include <iostream>

int file_size(char* filename)
{
	FILE *fp=fopen(filename,"r");
	if(!fp) return -1;
	fseek(fp,0L,SEEK_END);
	int size=ftell(fp);
	fclose(fp);

	return size;
}

static llvm::cl::opt<std::string> FileName(llvm::cl::Positional, llvm::cl::desc("Input file"), llvm::cl::Required);
int main(int argc, char **argv)
{
	llvm::cl::ParseCommandLineOptions(argc, argv, "preprocess test\n");

	llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> p_diag_option(new clang::DiagnosticOptions());
	clang::TextDiagnosticPrinter* p_diag_printer = new clang::TextDiagnosticPrinter(llvm::outs(), p_diag_option.get());
	llvm::IntrusiveRefCntPtr<clang::DiagnosticIDs> p_diag_ids(new clang::DiagnosticIDs());

	clang::DiagnosticsEngine* pDiagnosticsEngine = new clang::DiagnosticsEngine(p_diag_ids,
				p_diag_option,
				p_diag_printer);

	clang::LangOptions languageOptions;

	clang::CompilerInstance ci;

	// ci.createDiagnostics();
	ci.setDiagnostics(pDiagnosticsEngine);
	std::shared_ptr<clang::TargetOptions> pto = std::make_shared<clang::TargetOptions>();
	pto->Triple = llvm::sys::getDefaultTargetTriple();
	clang::TargetInfo *pti= clang::TargetInfo::CreateTargetInfo(ci.getDiagnostics(), pto);
	ci.setTarget(pti);

	ci.createFileManager();
	ci.createSourceManager(ci.getFileManager());

	std::shared_ptr<clang::TargetOptions> targetOptions = std::make_shared<clang::TargetOptions>();
	targetOptions->Triple = llvm::sys::getDefaultTargetTriple();

	clang::TargetInfo *pTargetInfo =
		clang::TargetInfo::CreateTargetInfo(
				*pDiagnosticsEngine,
				targetOptions);

	std::shared_ptr<clang::HeaderSearchOptions> hso = std::make_shared<clang::HeaderSearchOptions>();
	clang::HeaderSearch headerSearch(hso,
			ci.getSourceManager(),
			*pDiagnosticsEngine,
			languageOptions,
			pTargetInfo);

	std::shared_ptr<clang::PreprocessorOptions> pOpts = std::make_shared<clang::PreprocessorOptions>();

	clang::Preprocessor preprocessor(
			pOpts,
			*pDiagnosticsEngine,
			languageOptions,
			ci.getSourceManager(),
			headerSearch,
			(clang::ModuleLoader &)ci
			);

	const clang::FileEntry *file = ci.getFileManager().getVirtualFile(FileName, file_size(argv[1]), 0);
	clang::FileID mainFileID = ci.getSourceManager().createFileID(file, clang::SourceLocation(), clang::SrcMgr::C_User);
	ci.getSourceManager().setMainFileID(mainFileID);

	preprocessor.EnterMainSourceFile();

	p_diag_printer->BeginSourceFile(languageOptions, &preprocessor);
	clang::Token token;
	do {
		preprocessor.Lex(token);
		if(pDiagnosticsEngine->hasErrorOccurred())
		{
			break;
		}
		preprocessor.DumpToken(token);
		std::cerr << std::endl;
	} while(token.isNot(clang::tok::eof));
	p_diag_printer->EndSourceFile();

	p_diag_option.reset();

	return 0;
}
